﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Chefbook.HelperMethods;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Chefbook.DataObjects
{
    /// <summary>
    /// Represents a recipe
    /// </summary>
    public class Recipe : INotifyPropertyChanged
    {
        private ObservableCollection<Category> categories = new ObservableCollection<Category>();
        private ObservableCollection<Ingredient> ingredients = new ObservableCollection<Ingredient>();
        private string name, description, directions;

        /// <summary>
        /// Creates a new Recipe in the specified category
        /// </summary>
        /// <param name="parent"></param>
        public Recipe(Category parent)
        {
            this.categories.AddIfNewAndNotNull(parent);
            parent.PerformIfNotNull(() => parent.AddRecipe(this));
            Name = string.Empty;
            Description = string.Empty;
            Directions = string.Empty;
        }

        /// <summary>
        /// Gets or sets the Name of the Recipe
        /// </summary>
        public string Name
        {
            get { return name; }
            set
            {
                if (name != value)
                {
                    name = value.ThrowIfNull("Name");
                    OnPropertyChanged("Name");
                }
            }
        }

        /// <summary>
        /// Gets or sets the Description of the Recipe
        /// </summary>
        public string Description
        {
            get { return description; }
            set
            {
                if (description != value)
                {
                    description = value.ThrowIfNull("Description");
                    OnPropertyChanged("Description");
                }
            }
        }

        /// <summary>
        /// Gets or sets the Directions for how to make the Recipe
        /// </summary>
        public string Directions
        {
            get { return directions; }
            set
            {
                if (directions != value)
                {
                    directions = value.ThrowIfNull("Directions");
                    OnPropertyChanged("Directions");
                }
            }
        }

        private TimeSpan prepTime;
        /// <summary>
        /// Gets or sets how much preparation time the Recipe requires
        /// </summary>
        public TimeSpan PrepTime
        {
            get { return prepTime; }
            set
            {
                if (prepTime != value)
                {
                    prepTime = value;
                    OnPropertyChanged("PrepTime");
                }
            }
        }

        private TimeSpan cookTime;
        /// <summary>
        /// Gets or sets how long the Recipe needs to cook
        /// </summary>
        public TimeSpan CookTime
        {
            get { return cookTime; }
            set
            {
                if (cookTime != value)
                {
                    cookTime = value;
                    OnPropertyChanged("CookTime");
                }
            }
        }

        /// <summary>
        /// Gets a readonlylist of categories this recipe belongs to
        /// </summary>
        public ReadOnlyObservableCollection<Ingredient> Ingredients
        {
            get { return new ReadOnlyObservableCollection<Ingredient>(ingredients); }
        }

        /// <summary>
        /// Gets a readonlylist of categories this recipe belongs to
        /// </summary>
        public ReadOnlyObservableCollection<Category> Categories
        {
            get { return new ReadOnlyObservableCollection<Category>(categories); }
        }

        /// <summary>
        /// Removes this recipe from all categories
        /// </summary>
        public void Delete()
        {
            var tempCategories = new List<Category>(categories);
            foreach (var cat in tempCategories)
            {
                cat.RemoveRecipe(this);
            }
        }

        /// <summary>
        /// Moves a recipe from one category to another
        /// </summary>
        /// <param name="from">The originating category</param>
        /// <param name="to">The category to move to</param>
        public void ChangeCategory(Category from, Category to)
        {
            from.ThrowIfNull("from");
            to.ThrowIfNull("to");
            ParameterValidation.ValidateArgument(() => categories.Contains(from), "from");

            to.AddRecipe(this);
            from.RemoveRecipe(this);
        }

        /// <summary>
        /// Adds this recipe to a category
        /// </summary>
        /// <param name="categoryToAdd"></param>
        public void AddCategory(Category categoryToAdd)
        {
            categoryToAdd.ThrowIfNull("categoryToAdd");
            if (categories.AddIfNewAndNotNull(categoryToAdd))
            {
                categoryToAdd.AddRecipe(this);
            }
        }

        /// <summary>
        /// Removes this recipe from a category
        /// </summary>
        /// <param name="categoryToRemove">The category to remove from</param>
        public void RemoveCategory(Category categoryToRemove)
        {
            categoryToRemove.ThrowIfNull("categoryToRemove");
            ParameterValidation.ValidateArgument(()=> this.categories.Contains(categoryToRemove), "categoryToRemove");
            categories.Remove(categoryToRemove);
            if (categoryToRemove.DirectRecipes.Contains(this))
            {
                categoryToRemove.RemoveRecipe(this);
            }
        }

        /// <summary>
        /// Adds an ingredient to the recipe
        /// </summary>
        /// <param name="ingredientToAdd">The ingredient to add</param>
        public void AddIngredient(Ingredient ingredientToAdd)
        {
            if (ingredients.AddIfNewAndNotNull(ingredientToAdd))
            {
                ingredientToAdd.Recipe = this;
            }
        }

        /// <summary>
        /// Removes an ingredient from the recipe
        /// </summary>
        /// <param name="ingredientToRemove"></param>
        public void RemoveIngredient(Ingredient ingredientToRemove)
        {
            ingredientToRemove.ThrowIfNull("ingredientToRemove");
            ParameterValidation.ValidateArgument(() => ingredients.Contains(ingredientToRemove), "ingredientToRemove");
            ingredients.Remove(ingredientToRemove);
        }

        /// <summary>
        /// Returns the name of the recipe
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Name;
        }

        /// <summary>
        /// Checks whether this Recipe has value equality to the parameter passed in
        /// </summary>
        /// <param name="value">The Recipe to check this against</param>
        /// <returns>True of they are equal in value, otherwise false</returns>
        public bool ValueEquals(object value)
        {
            var rec = value as Recipe;
            if (rec == null) return false;
            if (!(this.Description.NullSafeEquals(rec.Description) &&
                this.Directions.NullSafeEquals(rec.Directions) &&
                this.Name.NullSafeEquals(rec.Name) &&
                this.PrepTime == rec.PrepTime &&
                this.CookTime == rec.CookTime))
            { return false; }

            if (this.ingredients.Count != rec.ingredients.Count)
            { return false; }
            foreach (var pair in this.ingredients.Zip(rec.ingredients, (i1, i2) => new { value1 = i1, value2 = i2 }))
            {
                if (!pair.value1.ValueEquals(pair.value2))
                { return false; }
            }

            return true;
        }

        /// <summary>
        /// Creates a memberwise clone of a recipe
        /// </summary>
        /// <returns>The cloned recipe</returns>
        public new Recipe MemberwiseClone()
        {
            var clone = new Recipe(new Category(null));
            clone.categories.Clear();
            foreach (var cat in this.categories)
            {
                clone.categories.Add(cat);
            }
            foreach (var ing in this.ingredients)
            {
                clone.ingredients.Add(ing);
            }
            clone.name = this.name;
            clone.description = this.description;
            clone.directions = this.directions;
            clone.CookTime = this.CookTime;
            clone.PrepTime = this.PrepTime;
            return clone;
        }

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

    }

}